package com.frank.oj.manager;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileReader;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.frank.model.entity.problem.Problem;
import com.frank.model.entity.problem.ProblemCase;
import com.frank.oj.common.exception.StatusFailException;
import com.frank.oj.common.exception.StatusForbiddenException;
import com.frank.oj.common.exception.StatusSystemErrorException;
import com.frank.oj.common.result.ResultStatus;
import com.frank.oj.constant.Constants;
import com.frank.oj.service.entity.problem.ProblemCaseEntityService;
import com.frank.oj.service.entity.problem.ProblemEntityService;
import com.frank.oj.shiro.AccountProfile;
import com.frank.oj.validator.GroupValidator;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author frank
 * @Date 2024/4/5
 */
@Component
@Slf4j(topic = "hcoj")
public class TestCaseManager {
    @Resource
    private ProblemCaseEntityService problemCaseEntityService;

    @Resource
    private ProblemEntityService problemEntityService;

    @Resource
    private GroupValidator groupValidator;

    public void downloadTestcase(Long pid, HttpServletResponse response) throws StatusForbiddenException, StatusFailException {
        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        boolean isRoot = SecurityUtils.getSubject().hasRole("root");
        boolean isProblemAdmin = SecurityUtils.getSubject().hasRole("problem_admin");

        Problem problem = problemEntityService.getById(pid);
        Long gid = problem.getGid();
        if (gid != null) {
            if (!isRoot &&
                    !problem.getAuthor().equals(userRole.getUsername()) &&
                    !groupValidator.isGroupMember(userRole.getUid(), gid)) {
                throw new StatusForbiddenException("对不起，您没有下载该题目测试用例的权限！");
            }
        } else {
            if (!isRoot && !isProblemAdmin && !problem.getAuthor().equals(userRole.getUsername())) {
                throw new StatusForbiddenException("对不起，您没有下载该题目测试用例的权限！");
            }
        }

        String workDir = Constants.File.TESTCASE_BASE_FOLDER.getPath() + File.separator + "problem_" + pid;
        File file = new File(workDir);
        if (!file.exists()) { // 若文件不存在，则在数据库中查找
            LambdaQueryWrapper<ProblemCase> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(ProblemCase::getPid, pid);
            List<ProblemCase> problemCaseList = problemCaseEntityService.list(queryWrapper);

            if (CollectionUtils.isEmpty(problemCaseList)) {
                throw new StatusFailException("对不起，该题目的评测数据为空！");
            }

            boolean hasTestCase = !problemCaseList.get(0).getInput().endsWith(".in") || (!problemCaseList.get(0).getOutput().endsWith(".out") &&
                    !problemCaseList.get(0).getOutput().endsWith(".ans"));
            if (!hasTestCase) {
                throw new StatusFailException("对不起，该题目的评测数据为空！");
            }

            FileUtil.mkdir(workDir);

            for (int i = 0; i < problemCaseList.size(); i++) {
                String filePreName = workDir + File.separator + (i + 1);
                String inputName = filePreName + ".in";
                String outputName = filePreName + ".out";
                FileWriter infileWriter = new FileWriter(inputName);
                infileWriter.write(problemCaseList.get(i).getInput());
                FileWriter outfileWriter = new FileWriter(outputName);
                outfileWriter.write(problemCaseList.get(i).getOutput());
            }
        }
        // 生成唯一的文件名，以当前时间戳为区分
        String fileName = "HC_problem_" + pid + "_testcase_" + System.currentTimeMillis() + ".zip";
        // 将指定目录下的文件打包成zip
        ZipUtil.zip(workDir, Constants.File.FILE_DOWNLOAD_TMP_FOLDER.getPath() + File.separator + fileName);

        // 准备读取压缩文件
        FileReader fileReader = new FileReader(Constants.File.FILE_DOWNLOAD_TMP_FOLDER.getPath() + File.separator + fileName);
        //放到缓冲流里面
        BufferedInputStream bins = new BufferedInputStream(fileReader.getInputStream());
        OutputStream outs = null;
        BufferedOutputStream bouts = null;

        try {
            // 获取响应输出流，准备向客户端发送文件
            outs = response.getOutputStream();
            bouts = new BufferedOutputStream(outs);
            // 设置响应类型，使得浏览器以下载方式处理文件
            response.setContentType("application/x-download");
            // 设置响应头，指定文件名
            response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            // 读取文件内容并写入响应流
            int bytesRead = 0;
            byte[] buffer = new byte[1024 * 10];
            while ((bytesRead = bins.read(buffer, 0, 1024 * 10)) != -1) {
                bouts.write(buffer, 0, bytesRead);
            }
            // 确保数据全部写入
            bouts.flush();
        } catch (IOException e) {
            // 记录下载异常
            log.error("下载题目测试数据的压缩文件异常------------>{}", e.getMessage());
            // 重置响应，以处理异常情况
            response.reset();
            // 设置响应类型为JSON，准备返回错误信息
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map<String, Object> map = new HashMap<>();
            map.put("status", ResultStatus.SYSTEM_ERROR);
            map.put("msg", "下载文件失败，请重新尝试！");
            map.put("data", null);
            try {
                // 返回错误信息
                response.getWriter().println(JSONUtil.toJsonStr(map));
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        } finally {
            // 尝试关闭所有资源
            try {
                bins.close();
                if (outs != null) {
                    outs.close();
                }
                if (bouts != null) {
                    bouts.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            // 删除临时文件
            FileUtil.del(Constants.File.FILE_DOWNLOAD_TMP_FOLDER.getPath() + File.separator + fileName);
            // 记录操作日志
            log.info("[{}],[{}],pid:[{}],operatorUid:[{}],operatorUsername:[{}]",
                    "Test_Case", "Download", pid, userRole.getUid(), userRole.getUsername());
        }


    }


    /**
     * 上传测试用例压缩包
     *
     * @param file 上传的文件，期望是一个压缩包
     * @param gid 组ID，用于判断用户是否有权限操作这个组的测试用例
     * @param mode 上传模式，用于后续处理测试用例的方式
     * @return 返回一个包含文件列表和文件目录的Map
     * @throws StatusForbiddenException 如果用户没有权限进行操作
     * @throws StatusFailException 如果上传的文件格式不正确或者上传过程中发生错误
     * @throws StatusSystemErrorException 如果在处理文件时发生系统错误
     */
    public Map<Object, Object> uploadTestcaseZip(MultipartFile file, Long gid, String mode) throws StatusForbiddenException, StatusFailException, StatusSystemErrorException {

        // 安全性检查：验证用户角色
        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        boolean isRoot = SecurityUtils.getSubject().hasRole("root");
        boolean isProblemAdmin = SecurityUtils.getSubject().hasRole("problem_admin");
        boolean isAdmin = SecurityUtils.getSubject().hasRole("admin");

        // 权限验证
        if (!isRoot && !isProblemAdmin && !isAdmin
                && !(gid != null && groupValidator.isGroupAdmin(userRole.getUid(), gid))) {
            throw new StatusForbiddenException("对不起，您无权限操作！");
        }

        // 检查文件格式
        String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1);
        if (!"zip".toUpperCase().contains(suffix.toUpperCase())) {
            throw new StatusFailException("请上传zip格式的测试数据压缩包！");
        }

        // 生成临时文件目录和文件路径
        String fileDirId = IdUtil.simpleUUID();
        String fileDir = Constants.File.TESTCASE_TMP_FOLDER.getPath() + File.separator + fileDirId;
        String filePath = fileDir + File.separator + file.getOriginalFilename();

        // 保存文件到指定目录
        FileUtil.mkdir(fileDir);
        try {
            file.transferTo(new File(filePath));
        } catch (IOException e) {
            log.error("评测数据文件上传异常-------------->{}", e.getMessage());
            throw new StatusSystemErrorException("服务器异常：评测数据上传失败！");
        }

        // 解压文件到目录，并删除原zip文件
        ZipUtil.unzip(filePath, fileDir);
        FileUtil.del(filePath);

        // 验证解压后的文件是否存在
        File testCaseFileList = new File(fileDir);
        File[] files = testCaseFileList.listFiles();
        if (files == null || files.length == 0) {
            FileUtil.del(fileDir);
            throw new StatusFailException("评测数据压缩包里文件不能为空！");
        }

        // 初始化输入输出数据映射
        HashMap<String, String> inputData = new HashMap<>();
        HashMap<String, String> outputData = new HashMap<>();

        // 遍历文件，将输入输出文件分别映射
        for (File tmp : files) {
            String tmpPreName = null;
            if (tmp.getName().endsWith(".in")) {
                tmpPreName = tmp.getName().substring(0, tmp.getName().lastIndexOf(".in"));
                inputData.put(tmpPreName, tmp.getName());
            } else if (tmp.getName().endsWith(".out")) {
                tmpPreName = tmp.getName().substring(0, tmp.getName().lastIndexOf(".out"));
                outputData.put(tmpPreName, tmp.getName());
            } else if (tmp.getName().endsWith(".ans")) {
                tmpPreName = tmp.getName().substring(0, tmp.getName().lastIndexOf(".ans"));
                outputData.put(tmpPreName, tmp.getName());
            } else if (tmp.getName().endsWith(".txt")) {
                tmpPreName = tmp.getName().substring(0, tmp.getName().lastIndexOf(".txt"));
                if (tmpPreName.contains("input")) {
                    inputData.put(tmpPreName.replaceAll("input", "$*$"), tmp.getName());
                } else if (tmpPreName.contains("output")) {
                    outputData.put(tmpPreName.replaceAll("output", "$*$"), tmp.getName());
                }
            }
        }

        // 根据输入输出文件生成测试用例列表
        List<HashMap<String, Object>> problemCaseList = new LinkedList<>();
        for (String key : inputData.keySet()) {
            HashMap<String, Object> testcaseMap = new HashMap<>();
            String inputFileName = inputData.get(key);
            testcaseMap.put("input", inputFileName);

            // 自动生成缺失的输出文件
            String oriOutputFileName = outputData.getOrDefault(key, null);
            if (oriOutputFileName == null) {
                oriOutputFileName = key + ".out";
                if (inputFileName.endsWith(".txt")) {
                    oriOutputFileName = inputFileName.replaceAll("input", "output");
                }
                FileWriter fileWriter = new FileWriter(fileDir + File.separator + oriOutputFileName);
                fileWriter.write("");
            }

            testcaseMap.put("output", oriOutputFileName);
            // 设置组数，用于后续的子任务处理
            if (Objects.equals(Constants.JudgeCaseMode.SUBTASK_LOWEST.getMode(), mode)
                    || Objects.equals(Constants.JudgeCaseMode.SUBTASK_AVERAGE.getMode(), mode)) {
                testcaseMap.put("groupNum", 1);
            }
            problemCaseList.add(testcaseMap);
        }

        // 根据文件名排序测试用例
        List<HashMap<String, Object>> fileList = problemCaseList.stream()
                .sorted((o1, o2) -> {
                    String input1 = (String) o1.get("input");
                    String input2 = (String) o2.get("input");
                    String a = input1.split("\\.")[0];
                    String b = input2.split("\\.")[0];
                    if (a.length() > b.length()) {
                        return 1;
                    } else if (a.length() < b.length()) {
                        return -1;
                    }
                    return a.compareTo(b);
                })
                .collect(Collectors.toList());

        // 返回处理结果
        return MapUtil.builder()
                .put("fileList", fileList)
                .put("fileListDir", fileDir)
                .map();
    }
}
